/*
 * Copyright (c) 2007 Aleksey Nikiforov
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'Aleksey Nikiforov' nor the names of other contributors 
 *   may be used to endorse or promote products derived from this software 
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.lex.input.effects.interpolation;

import org.lex.input.effects.component.SpringComponent;
import org.lex.input.effects.component.SpringConfig;

import com.jme.math.FastMath;
import com.jme.math.Quaternion;

/**
 * This class IS NOT an accurate physical simulation, because it does not keep
 * the rotational momentum for all three axis of rotation.
 * This class is ment to be used for special effects and not for simulating
 * the real world rotation under the spring tension.
 * 
 * @author lex
 *
 */
public class SpringQuaternionInterpolation
	extends SpringConfig
	implements GoalQuaternionInterpolation
{

	private float initialSpringConstant;
	private Quaternion goal = new Quaternion();
	private Quaternion current = new Quaternion();
	
	private Quaternion temp = new Quaternion();
	private Quaternion temp1 = new Quaternion();
	
	private SpringComponent spring;
	private float maxSpeed;
	private float defaultMaxSpeed;
	private boolean acceleratePastInitial;
	private float snapDistance;
	private float accelerationFactor = 1000; 
	
	private float angleCountdown;
	private float timeCount;
	
	public SpringQuaternionInterpolation(float mass,
			float springConstant, float damping)
	{
		super(mass, springConstant, damping);
		initialSpringConstant = springConstant;
		spring = SpringComponent.getForConfig(this);
		passOverAllowed = false;
	}
	
	public void clear() {
		current.set(goal);
		spring.stop();
		angleCountdown = 0;
		k = initialSpringConstant;
		timeCount = 0;
		maxSpeed = defaultMaxSpeed;
	}

	public Quaternion getCurrent() {
		return current;
	}

	public void init(Quaternion current, Quaternion goal) {
		this.current.set(current);
		this.goal.set(goal);
		spring.stop();
		angleCountdown = getCurrentGoalAngle();
		k = initialSpringConstant;
		timeCount = 0;
		maxSpeed = defaultMaxSpeed;
	}

	public void setCurrent(Quaternion current) {
		this.current.set(current);
	}

	public void setGoal(Quaternion goal) {
		this.goal.set(goal);
	}

	public void update(float deltaT) {
		float angle = getCurrentGoalAngle();
        
        if (angle > FastMath.ZERO_TOLERANCE
        		&& (360 - angle) > FastMath.ZERO_TOLERANCE)
        {
        	if (acceleratePastInitial) {
        		if (angle <= snapDistance) angleCountdown = 0;
        		if(angleCountdown <= 0) {
        			maxSpeed = 0;
	        		timeCount += deltaT;
					k = initialSpringConstant*FastMath.pow(
							accelerationFactor, timeCount);
        		}
			}
        	
	        spring.setDisplacement(angle);
	        spring.update(deltaT);
	        
	        float angleChange = spring.getTravelVector();
	        if (maxSpeed != 0 && Math.abs(angleChange/deltaT) > maxSpeed) {
	        	angleChange = FastMath.copysign(1, angleChange)*maxSpeed*deltaT;
	        }
	        
	        float interpolationPercent = angleChange/angle;
	        current.slerp(goal, interpolationPercent);
	        
	        angleCountdown -= FastMath.abs(angleChange);
        } else {
        	spring.stop();
		}
		
		if (!isAlive()) {
			current.set(goal);
		}
	}
	
	private float getCurrentGoalAngle() {
		temp.set(current);
		Quaternion currentInverse = temp.inverseLocal();
		Quaternion rot = currentInverse.mult(goal, temp1);
		rot.normalize();
		
		float angle = 0;
		float sqrLength = rot.x*rot.x + rot.y*rot.y + rot.z*rot.z;
		if (sqrLength != 0.0f) angle = (2.0f * FastMath.acos(rot.w));
        angle *= FastMath.RAD_TO_DEG;
        
        return angle;
	}
	
	public boolean isAlive() {
		return (spring.getDisplacement() != 0);
	}

	public boolean isAcceleratingPastInitial() {
		return acceleratePastInitial;
	}

	public void setAcceleratePastInitial(boolean accelerate) {
		acceleratePastInitial = accelerate;
	}

	public float getSnapDistance() {
		return snapDistance;
	}

	/**
	 * @param snapDistance in degrees
	 * 
	 * @see org.lex.input.effects.interpolation.ClampedInterpolation#setSnapDistance(float)
	 */
	public void setSnapDistance(float snapDistance) {
		this.snapDistance = snapDistance;
	}

	public float getAccelerationFactor() {
		return accelerationFactor;
	}

	public void setAccelerationFactor(float factor) {
		accelerationFactor = factor;
	}

	public float getMaxSpeed() {
		return defaultMaxSpeed;
	}

	public void setMaxSpeed(float maxSpeed) {
		defaultMaxSpeed = maxSpeed;
		if (angleCountdown > 0) this.maxSpeed = maxSpeed;
	}

}
